package com.threaddemo;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;
import com.poidemo.ExcelModel;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;

import static java.util.concurrent.Executors.newFixedThreadPool;

/**
 * 直接用map接收数据
 *
 * @author haibo zhu
 */
public class ThreadDataListener extends AnalysisEventListener<Map<Integer, String>> {
    private static final Logger LOGGER = LoggerFactory.getLogger(ThreadDataListener.class);
    /**
     * 每隔5条存储数据库，实际使用中可以3000条，然后清理list ，方便内存回收
     */
    private int BATCH_COUNT = 50000;
    List<Map<Integer, String>> list = new ArrayList<>();
    private ExcelModel excelModel;
    private int fileCount = 0;
    private int currentIndex = 0;
    private final List<List<String>> headerList = new ArrayList<>();

    private static ExecutorService es = null;

    public ThreadDataListener() {
    }

    public ThreadDataListener(int BATCH_COUNT) {
        this.BATCH_COUNT = BATCH_COUNT;
    }

    /**
     * 这里会一行行的返回头
     *
     * @param headMap
     * @param context
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        headMap.forEach((k, v) -> {
            List<String> list = new ArrayList<>();
            list.add(v);
            headerList.add(list);
        });
        LOGGER.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
        LOGGER.info("列表头={}", head());
        if (null == this.excelModel) {
            String excelChildFileDir = context.readWorkbookHolder().getFile().getParentFile().getPath();
            excelModel = new ExcelModel(context.readSheetHolder().getSheetName(), getTotals(context), BATCH_COUNT, getFileCount(context), true, false);
            excelModel.setExcelChildFileDir(excelChildFileDir);
            es = newFixedThreadPool(getFileCount(context));
        }
    }

    @SneakyThrows
    @Override
    public void invoke(Map<Integer, String> data, AnalysisContext context) {
        list.add(data);
        currentIndex++;
        if (list.size() >= BATCH_COUNT) {
            es.execute(new ExcelThread(deepClone(), fileCount, currentIndex));
            fileCount++;
            list.clear();
        }
    }

    @SneakyThrows
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        es.execute(new ExcelThread(deepClone(), fileCount, currentIndex));
        es.shutdown();
        LOGGER.info("所有数据解析完成！");

    }

    /**
     * 处理数据
     */
    private void saveData(List<Map<Integer, String>> data, int fileCount, int currentIndex) {
        LOGGER.info("{}条数据，开始分割数据！", data.size());
        int endRow = fileCount * this.excelModel.getFileSize();
        int startRow = (endRow - this.excelModel.getFileSize()) + 1;
        startRow += BATCH_COUNT;
        endRow += BATCH_COUNT;
        if (fileCount == this.excelModel.getFileCount() - 1) {
            endRow = currentIndex;
        }
//        读取总行数不准确，空数据的行也算进去
//        if (endRow >= this.excelModel.getTotalRows()) {
//            endRow = this.excelModel.getTotalRows();
//        }

        String path = excelModel.getExcelChildFileDir() + File.separator;

        String fileName = this.excelModel.getSheetName() + startRow + "_" + endRow + ".xlsx";
        List<List<Object>> datalist = new ArrayList<>();

        data.forEach(map -> {
            List<Object> row = new ArrayList<>();
            map.forEach((k, v) -> row.add(v));
            datalist.add(row);
        });


        // 这里 需要指定写用哪个class去写，然后写到第一个sheet，名字为模板 然后文件流会自动关闭

        long beginTime = System.currentTimeMillis();
        EasyExcel.write(path + fileName).head(head()).useDefaultStyle(false).sheet(fileName).doWrite(datalist);
        LOGGER.info("分割文件输出路径{},执行耗时{}秒", path + fileName, (System.currentTimeMillis() - beginTime) / 1000f);

        LOGGER.info("数据写入文件成功！");
    }

    /**
     * 使用序列化实现深拷贝
     *
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public List<Map<Integer, String>> deepClone() throws IOException, ClassNotFoundException {
        long beginTime = System.currentTimeMillis();
        //将对象写入流中
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
        objectOutputStream.writeObject(list);
        //从流中取出
        ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        List<Map<Integer, String>> maps = (List<Map<Integer, String>>) objectInputStream.readObject();
        LOGGER.info("克隆耗时{}秒", (System.currentTimeMillis() - beginTime) / 1000f);
        return maps;
    }

    private List<List<String>> head() {
        return headerList;
    }

    private int getTotals(AnalysisContext context) {
        return context.readSheetHolder().getApproximateTotalRowNumber() - 1;
    }

    private int getFileCount(AnalysisContext context) {
        Integer total = context.readSheetHolder().getApproximateTotalRowNumber();
        int fileCount;
        if ((total - 1) % BATCH_COUNT == 0) {
            fileCount = (total - 1) / BATCH_COUNT;
        } else {
            fileCount = ((total - 1) / BATCH_COUNT) + 1;
        }
        System.out.println("fileCount:" + fileCount);
        return fileCount;
    }

    class ExcelThread implements Runnable {// 任务接口
        private List<Map<Integer, String>> data;
        private int fileCount;
        private int currentIndex;

        ExcelThread(List<Map<Integer, String>> data, int fileCount, int currentIndex) {
            this.data = data;
            this.fileCount = fileCount;
            this.currentIndex = currentIndex;
        }

        @Override
        public void run() {
            saveData(data, fileCount, currentIndex);
        }
    }

    public static boolean isNumeric(String str) {
        for (int i = str.length(); --i >= 0; ) {
            if (!Character.isDigit(str.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) throws Exception {
        // 文件路径参数，分割后每个文件的记录数
        String fileName = "C:\\Users\\zjlsz\\Desktop\\string.xlsx";
//        String file = System.getProperty("file");
//        String count = System.getProperty("count");
//        if (null != file){
//            fileName = file;
//        }else {
//            throw new Exception(MessageFormat.format("输入文件名：{0}不规范,请重新输入",file));
//        }
//        if (null==count || !isNumeric(count)){
//            throw new Exception(MessageFormat.format("分割条数：{0}类型不符合,请输入数字",count));
//        }
        long beginTime = System.currentTimeMillis();
        // 这里 只要，然后读取第一个sheet 同步读取会自动finish
        EasyExcel.read(fileName, new ThreadDataListener(1000)).sheet().doRead();
        LOGGER.info("源文件{},总执行耗时{}秒", fileName, (System.currentTimeMillis() - beginTime) / 1000f);
    }

}